/*
 * Copyright (C) 2011 Christopher Probst
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of the 'FoxNet Codec' nor the names of its 
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.foxnet.codec.impl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.nio.channels.ClosedChannelException;

import com.foxnet.codec.Cache;
import com.foxnet.codec.CachePolicy;
import com.foxnet.codec.CodecRegistry;

/**
 * @author Christopher Probst
 */
final class ByteBufferOStream extends AbstractOStream<ByteBuffer> {

    private ByteBuffer verifyBuffer(int minimalSize) throws IOException {
	if (isOpen()) {
	    if (resource != null) {
		if (resource.remaining() < minimalSize) {
		    expand(minimalSize);
		}
		return resource;
	    } else {
		throw new IOException("The resource is null.");
	    }
	} else {
	    throw new ClosedChannelException();
	}
    }

    private void expand(int minimalSize) {

	// Double the capacity or calc manually
	int newBufferSize =
		Math.max(resource.capacity() << 1, resource.position()
			+ minimalSize);

	// Save old ref
	ByteBuffer oldBuffer = resource;

	// Allocate new resource
	resource =
		resource.isDirect() ? ByteBuffer.allocateDirect(newBufferSize)
			: ByteBuffer.allocate(newBufferSize);

	// Flip the old resource
	oldBuffer.flip();

	// Copy the old content to the new resource
	resource.put(oldBuffer);
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.impl.AbstractIoStream#closeImpl()
     */
    @Override
    protected void closeImpl() throws IOException {
    }

    /*
     * (non-Javadoc)
     * @see
     * com.foxnet.codec.impl.AbstractOutput#writeByteBuffer(java.nio.ByteBuffer)
     */
    @Override
    protected void writeByteBuffer(ByteBuffer byteBuffer) throws IOException {
	verifyBuffer(byteBuffer.remaining()).put(byteBuffer);
    }

    /*
     * (non-Javadoc)
     * @see
     * com.foxnet.codec.impl.AbstractOutput#writeCharBuffer(java.nio.CharBuffer)
     */
    @Override
    protected void writeCharBuffer(CharBuffer charBuffer) throws IOException {
	int len = charBuffer.remaining() << 1;
	verifyBuffer(len).asCharBuffer().put(charBuffer);
	resource.position(resource.position() + len);
    }

    /*
     * (non-Javadoc)
     * @see
     * com.foxnet.codec.impl.AbstractOutput#writeShortBuffer(java.nio.ShortBuffer
     * )
     */
    @Override
    protected void writeShortBuffer(ShortBuffer shortBuffer) throws IOException {
	int len = shortBuffer.remaining() << 1;
	verifyBuffer(len).asShortBuffer().put(shortBuffer);
	resource.position(resource.position() + len);
    }

    /*
     * (non-Javadoc)
     * @see
     * com.foxnet.codec.impl.AbstractOutput#writeIntBuffer(java.nio.IntBuffer)
     */
    @Override
    protected void writeIntBuffer(IntBuffer intBuffer) throws IOException {
	int len = intBuffer.remaining() << 2;
	verifyBuffer(len).asIntBuffer().put(intBuffer);
	resource.position(resource.position() + len);
    }

    /*
     * (non-Javadoc)
     * @see
     * com.foxnet.codec.impl.AbstractOutput#writeLongBuffer(java.nio.LongBuffer)
     */
    @Override
    protected void writeLongBuffer(LongBuffer longBuffer) throws IOException {
	int len = longBuffer.remaining() << 3;
	verifyBuffer(len).asLongBuffer().put(longBuffer);
	resource.position(resource.position() + len);
    }

    /*
     * (non-Javadoc)
     * @see
     * com.foxnet.codec.impl.AbstractOutput#writeFloatBuffer(java.nio.FloatBuffer
     * )
     */
    @Override
    protected void writeFloatBuffer(FloatBuffer floatBuffer) throws IOException {
	int len = floatBuffer.remaining() << 2;
	verifyBuffer(len).asFloatBuffer().put(floatBuffer);
	resource.position(resource.position() + len);
    }

    /*
     * (non-Javadoc)
     * @see
     * com.foxnet.codec.impl.AbstractOutput#writeDoubleBuffer(java.nio.DoubleBuffer
     * )
     */
    @Override
    protected void writeDoubleBuffer(DoubleBuffer doubleBuffer)
	    throws IOException {
	int len = doubleBuffer.remaining() << 3;
	verifyBuffer(len).asDoubleBuffer().put(doubleBuffer);
	resource.position(resource.position() + len);
    }

    public ByteBufferOStream(CodecRegistry codecSystem, Cache cache,
	    CachePolicy cachePolicy, ByteBuffer resource) throws IOException {
	super(codecSystem, cache, cachePolicy);
	this.resource = resource;
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.impl.AbstractIoResource#getResource()
     */
    @Override
    public ByteBuffer getResource() {
	return (ByteBuffer) super.getResource().duplicate().flip();
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.Output#writeByte(int)
     */
    @Override
    public void writeByte(int byteValue) throws IOException {
	verifyBuffer(1).put((byte) byteValue);
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.Output#writeBoolean(boolean)
     */
    @Override
    public void writeBoolean(boolean booleanValue) throws IOException {
	verifyBuffer(1).put((byte) (booleanValue ? 1 : 0));
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.Output#writeChar(int)
     */
    @Override
    public void writeChar(int charValue) throws IOException {
	verifyBuffer(2).putChar((char) charValue);
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.Output#writeShort(int)
     */
    @Override
    public void writeShort(int shortValue) throws IOException {
	verifyBuffer(2).putShort((short) shortValue);
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.Output#writeInt(int)
     */
    @Override
    public void writeInt(int intValue) throws IOException {
	verifyBuffer(4).putInt(intValue);
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.Output#writeLong(long)
     */
    @Override
    public void writeLong(long longValue) throws IOException {
	verifyBuffer(8).putLong(longValue);
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.Output#writeFloat(float)
     */
    @Override
    public void writeFloat(float floatValue) throws IOException {
	verifyBuffer(4).putFloat(floatValue);
    }

    /*
     * (non-Javadoc)
     * @see com.foxnet.codec.Output#writeDouble(double)
     */
    @Override
    public void writeDouble(double doubleValue) throws IOException {
	verifyBuffer(8).putDouble(doubleValue);
    }
}
